package woowcrawler.html;

import com.sun.org.apache.xerces.internal.impl.xpath.regex.Match;
import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Classe que representa uma Tag HTML,
 * dado a seguinte tag <a href="link.com" class="css"><img src="img.jpg">link</a>
 *
 * é convertido para o objeto na seguinte forma
 *
 * tag={
 *  name:"a",
 *  attributes:[
 *      {
 *          name:"href",
 *          value:"link.com"
 *      },
 *      {
 *          name:"class",
 *          value:"css"
 *      }
 *  ],
 *  conteudo:"<img src='img.jpg'>link"
 * }
 *
 *
 * @author Leafarneo
 */
public class Tag {
    String name;
    ArrayList<Attribute> attributes;
    boolean isSelfClosing;
    String conteudo;

    /*variavel de controle global, nao tive uma ideia melhor*/
    private int lastIndex;

    public Tag() {
        attributes=new ArrayList<Attribute>();
    }

    public String getContent(){
        return this.conteudo;
    }

    public String getName(){
        return this.name;
    }

    public Attribute[] getAttributes(){
        Attribute[] a=new Attribute[this.attributes.size()];
        return this.attributes.toArray(a);
    }

    /**
     * @return O objeto convertido para uma String em HTML
     */
    @Override
    public String toString(){
        String retorno="<"+this.name;
        for (int i = 0; i < this.attributes.size(); i++) {
            retorno+=" "+attributes.get(i).toString();
        }
        if(this.isSelfClosing){
            retorno+="/>";
        }
        else{
            retorno+=">"+this.conteudo+"</"+this.name+">";
        }
        return retorno;
    }

    public String getCleanContent(){
        return this.conteudo.replaceAll("<[^<>]*>", "");
    }

    /**
     * Adiciona um novo atributo na Tag atual
     * @param nome nome do atributo
     * @param valor valor do atributo
     */
    public void AdicionarAtributo(String nome,String valor){
        attributes.add(new Attribute(nome, valor));
    }

    /**
     *
     * @param strTag uma String com uma Tag HTML
     * @return o objeto convertido para Tag
     */
    public static Tag FromString(String strTag){
        Tag tag=new Tag();
        tag.name=getTagName(strTag);
        tag.isSelfClosing=isSelfClosingTag(strTag);
        if(!tag.isSelfClosing){
            tag.conteudo= strTag.substring(strTag.indexOf(">")+1,strTag.lastIndexOf("<"));
        }
        Matcher m2=Pattern.compile("(?<=(<"+tag.name+"\\s?)).*?(?=(\\s?/?>))",Pattern.CASE_INSENSITIVE).matcher(strTag);
        if(m2.find()){
            if(m2.group().trim().length()>0){
                String[] atributos=m2.group().trim().split("(?<=\")\\s");
                for (int i = 0; i < atributos.length; i++) {
                    Matcher mNameAttr=Pattern.compile(".*?(?=(\\s?=))").matcher(atributos[i]);
                    mNameAttr.find();
                    Matcher mValueAttr=Pattern.compile("(?<=(\")).*?(?=(\"))").matcher(atributos[i]);
                    mValueAttr.find();
                    tag.AdicionarAtributo(mNameAttr.group(), mValueAttr.group());
                }
            }
        }
        return tag;
    }

    /**
     *
     * @param strTag uma tag HTML
     * @return o nome da tag
     */
    public static String getTagName(String strTag){
        Pattern p=Pattern.compile("(?<=<)(\\w+)(?=(\\s*|\\/|>))");
        Matcher m=p.matcher(strTag);
        if(m.find()){
            return m.group();
        }
        return null;
    }

    private static boolean isSelfClosingTag(String tagName){
        String[] tagSelfClosing={"br","img","hr","input","meta","spacer","link","base"};
        for (int i = 0; i < tagSelfClosing.length; i++) {
            if(tagSelfClosing[i].equalsIgnoreCase(tagName))
                return true;
        }
        return false;
    }

    /**
     *
     * @param tagPattern A expressão regular para a seleção de tags
     * @param index A partir de qual ponto do conteudo da Tag atual iniciar a busca
     * @return um string contendo a tag HTML que casou com o padrao "tagPatter"
     * @throws Exception Lança exceção se houver problemas sintaticos
     */
    public String getTagString(Pattern tagPattern, int index) throws Exception{
        Matcher m=tagPattern.matcher(this.conteudo.substring(index));
        if(m.find()){
            String tagStr=m.group();
            String nomeTag=getTagName(tagStr);
            lastIndex=m.start()+index;
            if(isSelfClosingTag(nomeTag)){
                return m.group();
            }
            else{
                int cont=m.end()+index;
                int flag=1;
                while(flag>0){
                    Matcher tagClose= Pattern.compile("</?"+nomeTag+"[^<>]*>",Pattern.CASE_INSENSITIVE).matcher(this.conteudo.substring(cont));
                    if(tagClose.find()){
                        if(tagClose.group().matches("</[^<>]*>"))
                            flag--;
                        else
                            flag++;
                        cont+=tagClose.end();
                    }
                    else{
                        throw new Exception("Não foi possivel localizar a tag de fechamento para a tag atual, com o Pattern :"+tagPattern.pattern()+" e com Padrao de tag de fechamento"+tagClose.pattern());
                    }
                }
                return this.conteudo.substring(lastIndex,cont);
            }

        }
        return null;
    }

    /**
     * Cria um Pattern para procurar tag pelo seu nome
     * @param name Nome da tag
     * @return Pattern para ser utilizada em uma consulta
     */
    public Pattern patternByName(String name){
        return Pattern.compile("<\\b"+name+"\\b\\s?[^<>]*>",Pattern.CASE_INSENSITIVE);
    }

    /**
     *
     * @param name nome da tag a ser procurada, exemplo: "a","p","h1"
     * @return Um Array de tags que tenha como nome o nome passado
     * @throws Exception
     */
    public Tag[] getTagsByName(String name) throws Exception{
        Pattern p = patternByName(name);
        int cont = 0;
        ArrayList<Tag> lista = new ArrayList<Tag>();
        String resultado = "";
        while ((resultado=getTagString(p, cont)) != null) {
            lista.add(Tag.FromString(resultado));
            cont=lastIndex+1;
        }
        Tag[] retorno = new Tag[lista.size()];
        return lista.toArray(retorno);
    }

    /**
     * Cria um Pattern para procurar tag pelo seu id
     * @param id id da tag
     * @return Pattern para ser utilizada em uma consulta
     */
    public Pattern patternById(String id){
        return Pattern.compile("<[^<>]*id\\s?=\\s?\""+id+"\"[^<>]*>",Pattern.CASE_INSENSITIVE);
    }

    /**
     *
     * @param id valor do atributo id da tag
     * @return o primeiro objeto Tag que tenha id igual ao id passado
     * @throws Exception
     */
    public Tag getTagById(String id) throws Exception{
        Pattern p = patternById(id);
        String resultado=getTagString(p, 0);
        if(resultado==null)
            return null;
        return Tag.FromString(resultado);
    }

    /**
     *
     * @param attributeName nome do atributo a ser filtrado, exemplo: "class", "title", "alt"
     * @param valueContains valor do atributo
     * @return Patter para consultas por valor de atributo
     */
    public Pattern patternByAttributeAndValue(String attributeName,String valueContains){
        return Pattern.compile("<[^<>]*"+attributeName+"\\s?=\\s?\""+valueContains+"\"[^<>]*>",Pattern.CASE_INSENSITIVE);
    }

    /**
     * 
     * @param attrName nome do atributo a ser filtrado, exemplo: "class", "title", "alt"
     * @param attrValue valor do atributo
     * @return Um array de Tag que tenha os atributos "attrName" com o valor "attrValue"
     * @throws Exception
     */
    public Tag[] getTagsByAttribute(String attrName, String attrValue) throws Exception{
        Pattern p = patternByAttributeAndValue(attrName, attrValue);
        int cont = 0;
        ArrayList<Tag> lista = new ArrayList<Tag>();
        String resultado = "";
        while ((resultado=getTagString(p, cont)) != null) {
            lista.add(Tag.FromString(resultado));
            cont=lastIndex+1;
        }
        Tag[] retorno = new Tag[lista.size()];
        return lista.toArray(retorno);
    }

    /**
     *
     * @param regex String contendo a Regex
     * @return conteudo resultado encontrado, ou null caso não encontre nada
     */
    public String getContentCustom(String regex){
        Pattern padrao=Pattern.compile(regex);
        Matcher m=padrao.matcher(this.conteudo);
        if(m.find()){
            return m.group();
        }
        return null;
    }

    /**
     * Exemplos de query, "a" "#titulo" ".css" "[alt=bla bla bla]" "p>b", é bem limitado,
     * mas permite a consulta por hierarquia
     *
     * @param query string que contem a query
     * @return lista de Tags
     */
    public Tag[] executeQuery(String query) throws Exception{
        if(query.contains(">")){
            ArrayList<Tag> lista=new ArrayList<Tag>();
            Tag[] retornoRecursivo= this.executeQuery(query.substring(0,query.indexOf(">")));
            for (int i = 0; i < retornoRecursivo.length; i++) {
                Tag[] outroRetorno=retornoRecursivo[i].executeQuery(query.substring(query.indexOf(">")+1));
                for (int j = 0; j < outroRetorno.length; j++) {
                    lista.add(outroRetorno[j]);

                }
            }
            Tag[] a=new Tag[lista.size()];
            return lista.toArray(a);
        }else{
            if(query.charAt(0)=='#'){
                Tag[] retorno={this.getTagById(query.substring(1))};
                return retorno;
            }
            else if(query.charAt(0)=='.'){
                return this.getTagsByAttribute("class", query.substring(1));
            }
            else{
                if(query.matches("\\[.*\\]")){
                    return this.getTagsByAttribute(query.substring(1,query.indexOf("=")), query.substring(query.indexOf("=")+1,query.length()-1));
                }
                else{
                    return this.getTagsByName(query);
                }
            }
        }

    }
}
